Skip to content

Visualizer: one height while stepping (embed) - #52

Merged
cedricvogel-leo merged 2 commits into
mainfrom
feature/steady-height
Nov 5, 2026
Merged

cedricvogel-leo merged 2 commits into
mainfrom
feature/steady-height

Conversation

@cedricvogel-leo

@cedricvogel-leo cedricvogel-leo commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

The visualizer keeps one height while stepping, in all modes, so the LiaScript page around the embed doesn't move. It comes before mode «Baum», as agreed.

What

In a compact host (the embed, whose height follows its content), while visualizing:

  • The visualizer is as tall as its tallest step from the start.
    • After the trace arrives, the workspace draws the likely tallest steps once, hidden (.visualizer-measure, at the canvas's width, inside the same container, so the same CSS applies), measures them, and keeps the tallest.
    • Which steps: every step of a trace of up to 120 steps; else the 80 with the most open calls (later ones first), the last step, and some spread over the trace (steadyHeightCandidates).
    • They are measured 12 per frame. That took about 0.1 s for a 225-step fib(6) and a few milliseconds for short traces.
    • The measurement belongs to one trace and mode. Until it's done, the view says data-steady-height="measuring", then "done".
    • The hidden copies are inert and aria-hidden, and they sit outside the canvas, which all of the visualizer's own DOM queries are scoped to.
    • A step taller still (in a long trace, one that wasn't measured) makes it grow when it comes. It never shrinks while stepping; that part existed before.
  • The console reserves the output of the last step from the start: an invisible copy of the whole output sits under the visible text (.visual-output-steady), so a print doesn't make it grow.
  • The cap: the embed caps the visualizer at 80% of the course window (windowHeight, which the macro passes; glpython-liascript PR to follow). A taller step scrolls inside the visualizer, which already follows the current step vertically and the active box horizontally.
    • With size=large the cap is at least the large visualizer's height.
    • Without windowHeight (an older macro), the screen's height stands in for the window's.
  • Starting again: a new visualization, a mode switch (Kasten ↔ Ersetzung) and a new width (iPad rotation) start again.
  • The IDE is unchanged: it isn't compact, its visualizer has a fixed height and scrolls already.

Refactor, no behaviour change: the step-dependent props of the root Frame are now plain functions: stepViewAt, declareTargetFor, pendingCallFor, framesWithPendingCall, collapsedCallsOf, inlineHostIn. The workspace's rootFrame(view) renders both the visible step and the measured ones, so the two can't drift apart.

Measured: embed height per step (Chrome, windowHeight=900)

Program Mode Steps main this PR
fakultaet(4) Kasten 49 6 heights, 470 → 1005 px 957 px throughout
fakultaet(4) Ersetzung 49 6 heights, 470 → 1031 px 957 px throughout
summe([3, 1, 4, 1, 5]) both 54 7 heights, up to 1138 px 957 px throughout
a loop with 6 print both 18 5 heights, 376 → 454 px 454 px throughout
fib(6) (more than 120 steps), 420 px wide both 225 7 heights, up to 1167 px 986 px throughout
nested calls in one long line, 420 px Ersetzung 29 523 → 547 px at the last step 547 px throughout

The 957 and 986 px rows are at the cap. At every step of these runs, the current line was inside the visualizer's view.

In the LiaScript player (headless Chrome, window 800 px, glpython-liascript branch feature/window-height, fakultaet(5) in Kasten, 59 steps): the iframe stays at 878 px. On main it grows in seven sizes, from 447 to 1089 px.

Checks

  • scripts/check-console-browser.mjs, new «steady height» block with windowHeight=900, for a recursion in Kasten and in Ersetzung and for a loop that prints:

    • one embed height for every step;
    • the visualizer at most 720 px;
    • the current line in the view at every step.

    I ran it against main's build: it fails there («one height for every step») and passes here.

  • Waiting for the measurement: the hidden copies have the same markup as the visible step, so a check that reads the visualizer during the measurement could find a variable twice. That's what failed in this PR's first CI run. All visualizer checks now wait for data-steady-height="done" first. I ran them with the measurement slowed down on purpose (one step per frame), and they pass.

  • Shared checks: a host's or a test's selector over the whole page can also match the hidden copies while measuring, for about 0.1 s. That's why the state is visible.

  • Locally, lint (the known warning only), test:runtime, test:protocol, test:hints, test:layout and test:console (twice) pass.

How it goes live

  1. Merging ships to the courses at once. The height becomes steady with every macro; the cap then uses the screen's height.
  2. The macro PR passes windowHeight, so the cap is 80% of the real course window. It works with the embed before this PR too, which ignores the parameter. Courses need a patch version bump for it.
  3. The IDE needs no bump for this; the next tag can carry it.

Manual tests (desktop, and iPad with an external keyboard)

In a LiaScript block:

  1. Recursion in Kasten: fakultaet(5) with @GLPython.eval(box). Visualize, then step forward and back with → and ←.
    • The text below the block doesn't move at any step.
    • From about the third box on, the boxes scroll inside the visualizer, and the current line stays in view.
  2. The same in Ersetzung, and with a recursive summe on lists.
  3. A loop with print: the console has its final height from the first step, and the lines appear in it one by one.
  4. Mode switch during a visualization: the height may change once, then stays.
  5. iPad: rotate during a visualization. The height adapts once, then stays while stepping.
  6. large (projector, @GLPython.visual(large)): at least as tall as before, still steady.
  7. A short program (print(1 + 2)): no empty area appears below the code.
  8. IDE (after a tag): the visualizer is as before.

Follow-ups (not fixed)

  • The block can still be taller than the window: the cap covers only the visualizer, at 80% of the window, plus the bar and the console. In an 800 px window, a deep recursion gives an 878 px block. If the whole block should fit the window, the cap would be the window minus the bar, the console and the macro's buttons. I didn't do that, so the console isn't squeezed.
  • Old embeds: for an older macro without windowHeight, the screen's height is a rough stand-in. A course window much smaller than the screen gets a cap that's too large.
  • In the IDE, the canvas's existing grow-only min-height also applies. It only adds scroll room there, as before.

🤖 Generated with Claude Code

cedricvogel-leo and others added 2 commits November 5, 2026 19:11
In a compact host (the embed, whose height follows its content) the canvas
is as tall as its tallest step from the start: up to 120 likely tallest
steps are drawn once, hidden, and measured, through the same StepView and
root Frame as the visible step. The console reserves the last step's
output. The embed caps the visualizer at 80% of the course window
(windowHeight, from the macro; else the screen's), so a taller step
scrolls inside it. The step-dependent parts of the root Frame's props are
now plain functions (stepViewAt and its helpers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… for it

The measuring state names the trace and mode it was made for, so a new
trace never renders the old candidates or reads as measured; the view
shows data-steady-height="measuring"/"done". The browser checks wait for
"done" before reading the visualizer, since the hidden copies have the
same markup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cedricvogel-leo
cedricvogel-leo merged commit 23ab3e6 into main Nov 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant